ci: OSS-Fuzz build integration, run on CI via ClusterFuzzLite - #51
Merged
Conversation
Adds the OSS-Fuzz build contract for the five Atheris harnesses already in
tests/fuzz/, and runs it on pull requests.
.clusterfuzzlite/Dockerfile base-builder-python + pinned requirements
.clusterfuzzlite/build.sh compiles every fuzz_*.py, zips its corpus
.clusterfuzzlite/project.yaml language, engine, sanitizers
On the upstream program itself: OSS-Fuzz's stated bar is that a project "must
have a significant user base and/or be critical to the global IT
infrastructure", which a self-hosted game control panel will not clear. That
is what ClusterFuzzLite is for — the same base images, the same build
contract, the same crash reporting, running on this repo's own CI with no
enrolment. If the project ever does qualify, these three files ARE the
submission: drop them in projects/linuxgsm-panel/ with a Dockerfile that
clones instead of copies.
Two things the OSS-Fuzz Python guide does not cover for a repo shaped like
this one:
- The panel is an application, not a pip package — there is no setup.py, so
`pip3 install .` does not apply. The repo root goes on PYTHONPATH instead,
which is both how the harnesses' imports resolve and how PyInstaller finds
those modules to bundle.
- The harnesses pre-load paramiko / eventlet.tpool / config through
importlib.import_module("..."), deliberately, so instrument_imports() only
instruments the parser. A string is invisible to PyInstaller's static
analysis, so those are named as --hidden-import; without them the targets
build clean and die on first import. bad-build-check is left ON precisely
because that is the failure mode here.
The workflow runs in code-change mode: it fuzzes what the diff touched, not
everything, and files a crash as SARIF in the Security tab next to the CodeQL
and Bandit alerts. run_fuzzers gets an explicit `language: python` — it
defaults to c++ and does not inherit the build step's setting.
This overlaps fuzz.yml on purpose: that is the broad every-target sweep, this
is the targeted one with real crash reporting. Dropping either is reasonable;
keeping both is what this does.
Batch fuzzing, corpus pruning and coverage are NOT enabled — all three need a
separate storage repo and a PAT to keep a corpus between runs. The README
says how to turn them on.
Verified: shellcheck clean, both YAML files parse, every corpus directory
resolves to a target. NOT verified locally — the Docker build itself; there
is no Docker on this machine. The README documents the helper.py commands to
check it, and CI is the first real run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
The first CI run failed, which is what it was there to do: bad_build_check
caught 3 of 5 targets dying at startup on
ModuleNotFoundError: No module named 'eventlet.hubs.epolls'
eventlet.hubs picks its implementation with a string, at import time:
builtin_hub_modules = tuple(importlib.import_module('eventlet.hubs.' + name)
for name in ('epolls', 'kqueue', 'poll', 'selects'))
so `--hidden-import eventlet.tpool` bundled tpool and none of the hubs it
transitively needs. Naming each hub by hand would work today and rot the next
time eventlet adds one, so this collects the whole package instead. Same for
paramiko (kex/cipher backends) and dns, which arrives transitively via
eventlet's greendns.
Kept the harnesses as they are rather than making their eventlet pre-load
optional: ssh_manager does tolerate eventlet being absent, but a fuzz target
whose import graph differs from production is testing something else.
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
Codacy's gate is zero new issues and it caught the one RUN line where $SRC was unquoted. The COPY lines are not shell, so they are unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the OSS-Fuzz build contract for the five Atheris harnesses already in
tests/fuzz/, and runs it on pull requests.First, the honest bit about the upstream program
OSS-Fuzz's stated acceptance bar is that a project "must have a significant user base and/or be critical to the global IT infrastructure". A self-hosted game control panel will not clear that, so submitting to https://github.com/google/oss-fuzz would almost certainly be declined.
ClusterFuzzLite is the answer to exactly that problem — same base images, same build contract, same crash reporting and dedup, running on this repo's own CI with no enrolment. That's what this PR wires up.
And if the project ever does qualify, these three files are the submission: drop them into
projects/linuxgsm-panel/with a Dockerfile that clones instead of copies, plus a contact email.What's here
Two things the OSS-Fuzz Python guide doesn't cover for a repo shaped like this
setup.py, so the guide'spip3 install .doesn't apply. The repo root goes onPYTHONPATHinstead — that's both how the harnesses'import ssh_managerresolves and how PyInstaller finds those modules to bundle.paramiko/eventlet.tpool/configviaimportlib.import_module("..."), deliberately, soinstrument_imports()only instruments the parser under test. A string is invisible to PyInstaller's static analysis, so those are declared as--hidden-import. Without them the targets build clean and die on their first import — which is whybad-build-checkis left on: that's precisely the failure mode here.Also worth flagging:
run_fuzzersdefaults tolanguage: c++and does not inherit the build step's setting, so it's set explicitly.Deliberate overlap with fuzz.yml
fuzz.ymlis the broad sweep — every target, fixed 60s, plain Atheris. This runs in code-change mode: only what the diff touched, with real crash dedup, filing findings as SARIF in the Security tab next to the CodeQL and Bandit alerts. Dropping either is reasonable; keeping both is what this does. Say the word if you'd rather I retirefuzz.yml.Not enabled
Batch fuzzing, corpus pruning and coverage all need a separate storage repo plus a PAT to keep a corpus between runs. Without one they'd start from the committed seeds every time and learn nothing.
.clusterfuzzlite/README.mddocuments how to turn them on.Verification
Done: shellcheck clean, both YAML files parse, every corpus directory resolves to a target, repo lint clean.
Not done: the Docker build itself — there's no Docker on this machine, so I could not run
helper.py build_fuzzers/check_build. The README lists those commands, and this PR's own CI run is the first real execution. If the build is wrong, this PR is where it shows.🤖 Generated with Claude Code